//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================


using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;
using Shp.Runtime.Sitka.ServiceAgent.Tests.Mocks;
using Shp.Runtime.Sitka.ServiceAgent.Managers;
using System.Collections.ObjectModel;
using Shp.Runtime.Sitka.ServiceAgent.Entities;

namespace Shp.Runtime.Sitka.ServiceAgent.Tests
{
    /// <summary>
    /// Summary description for CachedRepositoryFixture
    /// </summary>
    [TestClass]
    public class CachedRepositoryFixture
    {
        private static string tenantId = string.Format("LitwareHR.Tests_{0}", Guid.NewGuid());
        private static RepositoryCacheManager cache = new RepositoryCacheManager( "", new SlidingTime( TimeSpan.FromSeconds( 3 ) ), true, true );
        private static RepositoryCacheManager namedCache = new RepositoryCacheManager("Test Cache Manager", new SlidingTime(TimeSpan.FromSeconds(3)), true, false);
        
        #region Additional test attributes

        // Use ClassInitialize to run code before running the first test in the class
        [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext)
        {
            ContainerManager.DeleteContainer(tenantId);
            ContainerManager.CreateContainer(tenantId);
        }

        // Use ClassCleanup to run code after all tests in a class have run
        [ClassCleanup()]
        public static void MyClassCleanup()
        {
            ContainerManager.DeleteContainer(tenantId);
            cache.Dispose();
            namedCache.Dispose();
        }

        #endregion


        [TestMethod]
        public void ShouldCreateSimpleEntityGetFromCacheAndDeleteIt()
        {
            //Creating a repository with a 3 seconds sliding window expiration policy for entities
            using (Repository<MockCacheableEntity> repository = new Repository<MockCacheableEntity>(tenantId, new MockCacheableEntityMapper(), cache ) )
            {
                Guid id = Guid.NewGuid();
                MockCacheableEntity obj = new MockCacheableEntity(id);

                repository.Insert(obj);

                MockCacheableEntity robj = repository.GetById(id);
                Assert.IsNotNull(robj);
                Assert.IsTrue(robj.IsCached);

                // Wait for slightly above the caching expiration time and retrive again
                Thread.Sleep(4000);

                robj = repository.GetById(id);
                Assert.IsNotNull(robj);
                Assert.IsFalse(robj.IsCached);

                repository.Delete(id);

                Assert.IsNull(repository.GetById(id));
            }
        }

        [TestMethod]
        public void ShouldNotCacheACacheableEntity()
        {
            //Creating a repository with a 3 seconds sliding window expiration policy for entities
            using (Repository<MockCacheableEntity> repository = new Repository<MockCacheableEntity>(tenantId, new MockCacheableEntityMapper(), null))
            {
                Guid id = Guid.NewGuid();
                MockCacheableEntity obj = new MockCacheableEntity(id);

                repository.Insert(obj);

                MockCacheableEntity robj = repository.GetById(id);
                Assert.IsNotNull(robj);
                Assert.IsFalse(robj.IsCached);

                //There's no cache, but try anyway
                Thread.Sleep(4000);

                robj = repository.GetById(id);
                Assert.IsNotNull(robj);
                Assert.IsFalse(robj.IsCached);

                repository.Delete(id);

                Assert.IsNull(repository.GetById(id));
            }
        }

        [TestMethod]
        public void ShouldCreateSimpleEntityGetFromNamedCacheAndDeleteIt()
        {
            //Creating a repository with a 3 seconds sliding window expiration policy for entities
            using (Repository<MockCacheableEntity> repository = new Repository<MockCacheableEntity>(tenantId, new MockCacheableEntityMapper(), namedCache))
            {
                Guid id = Guid.NewGuid();
                MockCacheableEntity obj = new MockCacheableEntity(id);

                repository.Insert(obj);

                MockCacheableEntity robj = repository.GetById(id);
                Assert.IsNotNull(robj);
                Assert.IsTrue(robj.IsCached);

                // Wait for slightly above the caching expiration time and retrive again
                Thread.Sleep(4000);

                robj = repository.GetById(id);
                Assert.IsNotNull(robj);
                Assert.IsFalse(robj.IsCached);

                repository.Delete(id);

                Assert.IsNull(repository.GetById(id));
            }
        }

        [TestMethod]
        public void ShouldInsertCachedEntityWithFieldAndSearchByIt()
        {
            using (Repository<MockExtendedEntity> repository = new Repository<MockExtendedEntity>(tenantId, new MockExtendedEntityMapper(), cache))
            {
                Guid id1 = Guid.NewGuid();
                Guid id2 = Guid.NewGuid();
                string tag = "Tag 1";
                MockExtendedEntity obj1 = new MockExtendedEntity(id1, tag);
                MockExtendedEntity obj2 = new MockExtendedEntity(id2, tag);

                repository.Insert(obj1);
                repository.Insert(obj2);

                string searchQuery = string.Format(@"where e[""{0}""]==""{1}""", "Tag", "Tag 1");
                List<MockExtendedEntity> found = new List<MockExtendedEntity>(repository.Search(searchQuery));

                Assert.AreEqual(2, found.Count);
                Assert.IsTrue(found.Exists(delegate(MockExtendedEntity mock) { return id1 == mock.Id; }));
                Assert.IsTrue(found.Exists(delegate(MockExtendedEntity mock) { return id2 == mock.Id; }));

                found.ForEach(delegate(MockExtendedEntity mock)
                {
                    Assert.AreEqual("Tag 1", mock.Tag);
                });

                repository.Delete(id1);
                repository.Delete(id2);

                Assert.IsNull(repository.GetById(id1));
                Assert.IsNull(repository.GetById(id2));
            }
        }

        [TestMethod]
        public void ShouldGetAllCachedEntitiesInserted()
        {
            using (Repository<MockCacheableEntity> repository = new Repository<MockCacheableEntity>(tenantId, new MockCacheableEntityMapper(), cache))
            {
                Guid Id1 = Guid.NewGuid();
                Guid Id2 = Guid.NewGuid();
                Guid Id3 = Guid.NewGuid();
                MockCacheableEntity[] entitiesArray = { new MockCacheableEntity(Id1), new MockCacheableEntity(Id2), new MockCacheableEntity(Id3) };
                Collection<MockCacheableEntity> entities = new Collection<MockCacheableEntity>(entitiesArray);

                repository.Insert(entities[0]);
                repository.Insert(entities[1]);
                repository.Insert(entities[2]);

                List<MockCacheableEntity> entitiesList = new List<MockCacheableEntity>(repository.LoadAll());

                Assert.AreEqual(3, entitiesList.Count);
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id1 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id2 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id3 == mock.Id; }));

                // First time they don;t come from the cache
                Assert.IsFalse(entitiesList[0].IsCached);
                Assert.IsFalse(entitiesList[1].IsCached);
                Assert.IsFalse(entitiesList[2].IsCached);


                entitiesList = new List<MockCacheableEntity>(repository.LoadAll());

                Assert.AreEqual(3, entitiesList.Count);
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id1 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id2 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id3 == mock.Id; }));

                // This time they should come from cache
                Assert.IsTrue(entitiesList[0].IsCached);
                Assert.IsTrue(entitiesList[1].IsCached);
                Assert.IsTrue(entitiesList[2].IsCached);

                Thread.Sleep(4000);

                entitiesList = new List<MockCacheableEntity>(repository.LoadAll());

                Assert.AreEqual(3, entitiesList.Count);
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id1 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id2 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id3 == mock.Id; }));

                Assert.IsFalse(entitiesList[0].IsCached);
                Assert.IsFalse(entitiesList[1].IsCached);
                Assert.IsFalse(entitiesList[2].IsCached);

                repository.Delete(Id1);
                repository.Delete(Id2);
                repository.Delete(Id3);
            }
        }

        //The named cache does NOT cache Queries
        [TestMethod]
        public void ShouldNotGetAllCachedEntitiesInserted()
        {
            using (Repository<MockCacheableEntity> repository = new Repository<MockCacheableEntity>(tenantId, new MockCacheableEntityMapper(), namedCache))
            {
                Guid Id1 = Guid.NewGuid();
                Guid Id2 = Guid.NewGuid();
                Guid Id3 = Guid.NewGuid();
                MockCacheableEntity[] entitiesArray = { new MockCacheableEntity(Id1), new MockCacheableEntity(Id2), new MockCacheableEntity(Id3) };
                Collection<MockCacheableEntity> entities = new Collection<MockCacheableEntity>(entitiesArray);

                repository.Insert(entities[0]);
                repository.Insert(entities[1]);
                repository.Insert(entities[2]);

                List<MockCacheableEntity> entitiesList = new List<MockCacheableEntity>(repository.LoadAll());

                Assert.AreEqual(3, entitiesList.Count);
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id1 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id2 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id3 == mock.Id; }));

                // First time they don't come from the cache
                Assert.IsFalse(entitiesList[0].IsCached);
                Assert.IsFalse(entitiesList[1].IsCached);
                Assert.IsFalse(entitiesList[2].IsCached);

                entitiesList = new List<MockCacheableEntity>(repository.LoadAll());

                Assert.AreEqual(3, entitiesList.Count);
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id1 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id2 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id3 == mock.Id; }));

                // Second time, should also come from the proxy (namedCache DOES NOT cache Query)
                Assert.IsFalse(entitiesList[0].IsCached);
                Assert.IsFalse(entitiesList[1].IsCached);
                Assert.IsFalse(entitiesList[2].IsCached);

                Thread.Sleep(4000);

                entitiesList = new List<MockCacheableEntity>(repository.LoadAll());

                Assert.AreEqual(3, entitiesList.Count);
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id1 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id2 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockCacheableEntity mock) { return Id3 == mock.Id; }));

                Assert.IsFalse(entitiesList[0].IsCached);
                Assert.IsFalse(entitiesList[1].IsCached);
                Assert.IsFalse(entitiesList[2].IsCached);

                repository.Delete(Id1);
                repository.Delete(Id2); 
                repository.Delete(Id3);

            }
        }

        [TestMethod]
        public void ShouldUpdateCachedEntityThenDeleteIt()
        {
            using (Repository<MockExtendedCacheableEntity> repository = new Repository<MockExtendedCacheableEntity>(tenantId, new MockExtendedCacheableEntityMapper(), cache))
            {
                Guid id = Guid.NewGuid();
                MockExtendedCacheableEntity obj = new MockExtendedCacheableEntity(id, "Original");

                repository.Insert(obj);

                obj = repository.GetById(id);
                Assert.AreEqual("Original", obj.Tag);
                Assert.IsTrue(obj.IsCached);

                obj.Tag = "Modified";
                repository.Update(obj);

                MockExtendedCacheableEntity robj = repository.GetById(id); 
                Assert.AreEqual("Modified", robj.Tag );
                Assert.IsTrue(robj.IsCached);

                Thread.Sleep(4000);

                robj = repository.GetById(id);
                Assert.AreEqual("Modified", robj.Tag);
                Assert.IsFalse(robj.IsCached);

                repository.Delete(id);
                Assert.IsNull(repository.GetById(id));
            }
        }
    }
}
